package com.chen.time.timing.interpolation;

import java.awt.Color;
import java.awt.geom.Arc2D;
import java.awt.geom.CubicCurve2D;
import java.awt.geom.Dimension2D;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.geom.QuadCurve2D;
import java.awt.geom.Rectangle2D;
import java.awt.geom.RoundRectangle2D;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;

public abstract class Evaluator<T> {

    /**
     * HashMap that holds all registered evaluators
     */
    private static final Map<Class<?>, Class<? extends Evaluator>> impls = new HashMap<Class<?>, Class<? extends Evaluator>>();

    /**
     * Static registration of pre-defined evaluators
     */
    static {
        impls.put(Byte.class, EvaluatorByte.class);
        impls.put(Short.class, EvaluatorShort.class);
        impls.put(Integer.class, EvaluatorInteger.class);
        impls.put(Long.class, EvaluatorLong.class);
        impls.put(Float.class, EvaluatorFloat.class);
        impls.put(Double.class, EvaluatorDouble.class);
        impls.put(Color.class, EvaluatorColor.class);
        impls.put(Point2D.class, EvaluatorPoint2D.class);
        impls.put(Line2D.class, EvaluatorLine2D.class);
        impls.put(Dimension2D.class, EvaluatorDimension2D.class);
        impls.put(Rectangle2D.class, EvaluatorRectangle2D.class);
        impls.put(RoundRectangle2D.class, EvaluatorRoundRectangle2D.class);
        impls.put(Ellipse2D.class, EvaluatorEllipse2D.class);
        impls.put(Arc2D.class, EvaluatorArc2D.class);
        impls.put(QuadCurve2D.class, EvaluatorQuadCurve2D.class);
        impls.put(CubicCurve2D.class, EvaluatorCubicCurve2D.class);
    }

    @SuppressWarnings("unchecked")
    static <T> Evaluator<T> create(Class<T> type) {
        Class<? extends Evaluator> interpClass = null;
        for (Class<?> klass : impls.keySet()) {
            if (klass.isAssignableFrom(type)) {
                interpClass = impls.get(klass);
                break;
            }
        }
        if (interpClass == null) {
            throw new IllegalArgumentException("No Evaluator" + " can be found for type " + type
                    + "; consider using" + " different types for your values or supplying a custom"
                    + " Evaluator");
        }
        try {
            Constructor<? extends Evaluator> ctor = interpClass.getConstructor();
            return ctor.newInstance();
        } catch (Exception e) {
            throw new IllegalArgumentException("Problem constructing " + "appropriate Evaluator for type "
                    + type + ":", e);
        }
    }

    /**
     * Abstract method to evaluate between two boundary values. Built-in
     * implementations all use linear parametric evaluation:
     * 
     * <pre>
     * v = v0 + (v1 - v0) * fraction
     * </pre>
     * 
     * Extenders of Evaluator will need to override this method and do something
     * similar for their own types. Note that this mechanism may be used to
     * create non-linear interpolators for specific value types, although it may
     * besimpler to just use the linear/parametric interpolation technique here
     * and perform non-linear interpolation through custom Interpolators rather
     * than perform custom calculations in this method; the point of this class
     * is to allow calculations with new/unknown types, not to provide another
     * mechanism for non-linear interpolation.
     */
    public abstract T evaluate(T v0, T v1, float fraction);
}

class EvaluatorByte extends Evaluator<Byte> {
    public EvaluatorByte() {
    }

    @Override
    public Byte evaluate(Byte v0, Byte v1, float fraction) {
        return (byte) (v0 + (byte) ((v1 - v0) * fraction));
    }
}

class EvaluatorShort extends Evaluator<Short> {
    public EvaluatorShort() {
    }

    @Override
    public Short evaluate(Short v0, Short v1, float fraction) {
        return (short) (v0 + (short) ((v1 - v0) * fraction));
    }
}

class EvaluatorInteger extends Evaluator<Integer> {
    public EvaluatorInteger() {
    }

    @Override
    public Integer evaluate(Integer v0, Integer v1, float fraction) {
        return v0 + (int) ((v1 - v0) * fraction);
    }
}

class EvaluatorLong extends Evaluator<Long> {
    public EvaluatorLong() {
    }

    @Override
    public Long evaluate(Long v0, Long v1, float fraction) {
        return v0 + (long) ((v1 - v0) * fraction);
    }
}

class EvaluatorFloat extends Evaluator<Float> {
    public EvaluatorFloat() {
    }

    @Override
    public Float evaluate(Float v0, Float v1, float fraction) {
        return v0 + (v1 - v0) * fraction;
    }
}

class EvaluatorDouble extends Evaluator<Double> {
    public EvaluatorDouble() {
    }

    @Override
    public Double evaluate(Double v0, Double v1, float fraction) {
        return v0 + (v1 - v0) * fraction;
    }
}

class EvaluatorColor extends Evaluator<Color> {
    public EvaluatorColor() {
    }

    @Override
    public Color evaluate(Color v0, Color v1, float fraction) {
        int r = v0.getRed() + (int) ((v1.getRed() - v0.getRed()) * fraction + 0.5f);
        int g = v0.getGreen() + (int) ((v1.getGreen() - v0.getGreen()) * fraction + 0.5f);
        int b = v0.getBlue() + (int) ((v1.getBlue() - v0.getBlue()) * fraction + 0.5f);
        int a = v0.getAlpha() + (int) ((v1.getAlpha() - v0.getAlpha()) * fraction + 0.5f);
        Color value = new Color(r, g, b, a);
        return value;
    }
}

class EvaluatorPoint2D extends Evaluator<Point2D> {
    // REMIND: apply this technique to other classes...
    private Point2D value;

    public EvaluatorPoint2D() {
    }

    @Override
    public Point2D evaluate(Point2D v0, Point2D v1, float fraction) {
        if (value == null) {
            // TODO: Note that future calls to this Evaluator may
            // use a different subclass of Point2D, so the precision of the
            // result may vary because we are caching a clone of
            // the first instance we received
            value = (Point2D) v0.clone();
        }
        double x = v0.getX() + (v1.getX() - v0.getX()) * fraction;
        double y = v0.getY() + (v1.getY() - v0.getY()) * fraction;
        value.setLocation(x, y);
        return value;
    }
}

class EvaluatorLine2D extends Evaluator<Line2D> {
    public EvaluatorLine2D() {
    }

    @Override
    public Line2D evaluate(Line2D v0, Line2D v1, float fraction) {
        double x1 = v0.getX1() + (v1.getX1() - v0.getX1()) * fraction;
        double y1 = v0.getY1() + (v1.getY1() - v0.getY1()) * fraction;
        double x2 = v0.getX2() + (v1.getX2() - v0.getX2()) * fraction;
        double y2 = v0.getY2() + (v1.getY2() - v0.getY2()) * fraction;
        Line2D value = (Line2D) v0.clone();
        value.setLine(x1, y1, x2, y2);
        return value;
    }
}

class EvaluatorDimension2D extends Evaluator<Dimension2D> {
    public EvaluatorDimension2D() {
    }

    @Override
    public Dimension2D evaluate(Dimension2D v0, Dimension2D v1, float fraction) {
        double w = v0.getWidth() + (v1.getWidth() - v0.getWidth()) * fraction;
        double h = v0.getHeight() + (v1.getHeight() - v0.getHeight()) * fraction;
        Dimension2D value = (Dimension2D) v0.clone();
        value.setSize(w, h);
        return value;
    }
}

class EvaluatorRectangle2D extends Evaluator<Rectangle2D> {
    public EvaluatorRectangle2D() {
    }

    @Override
    public Rectangle2D evaluate(Rectangle2D v0, Rectangle2D v1, float fraction) {
        double x = v0.getX() + (v1.getX() - v0.getX()) * fraction;
        double y = v0.getY() + (v1.getY() - v0.getY()) * fraction;
        double w = v0.getWidth() + (v1.getWidth() - v0.getWidth()) * fraction;
        double h = v0.getHeight() + (v1.getHeight() - v0.getHeight()) * fraction;
        Rectangle2D value = (Rectangle2D) v0.clone();
        value.setRect(x, y, w, h);
        return value;
    }
}

class EvaluatorRoundRectangle2D extends Evaluator<RoundRectangle2D> {
    public EvaluatorRoundRectangle2D() {
    }

    @Override
    public RoundRectangle2D evaluate(RoundRectangle2D v0, RoundRectangle2D v1, float fraction) {
        double x = v0.getX() + (v1.getX() - v0.getX()) * fraction;
        double y = v0.getY() + (v1.getY() - v0.getY()) * fraction;
        double w = v0.getWidth() + (v1.getWidth() - v0.getWidth()) * fraction;
        double h = v0.getHeight() + (v1.getHeight() - v0.getHeight()) * fraction;
        double arcw = v0.getArcWidth() + (v1.getArcWidth() - v0.getArcWidth()) * fraction;
        double arch = v0.getArcHeight() + (v1.getArcHeight() - v0.getArcHeight()) * fraction;
        RoundRectangle2D value = (RoundRectangle2D) v0.clone();
        value.setRoundRect(x, y, w, h, arcw, arch);
        return value;
    }
}

class EvaluatorEllipse2D extends Evaluator<Ellipse2D> {
    public EvaluatorEllipse2D() {
    }

    @Override
    public Ellipse2D evaluate(Ellipse2D v0, Ellipse2D v1, float fraction) {
        double x = v0.getX() + (v1.getX() - v0.getX()) * fraction;
        double y = v0.getY() + (v1.getY() - v0.getY()) * fraction;
        double w = v0.getWidth() + (v1.getWidth() - v0.getWidth()) * fraction;
        double h = v0.getHeight() + (v1.getHeight() - v0.getHeight()) * fraction;
        Ellipse2D value = (Ellipse2D) v0.clone();
        value.setFrame(x, y, w, h);
        return value;
    }
}

class EvaluatorArc2D extends Evaluator<Arc2D> {
    public EvaluatorArc2D() {
    }

    @Override
    public Arc2D evaluate(Arc2D v0, Arc2D v1, float fraction) {
        double x = v0.getX() + (v1.getX() - v0.getX()) * fraction;
        double y = v0.getY() + (v1.getY() - v0.getY()) * fraction;
        double w = v0.getWidth() + (v1.getWidth() - v0.getWidth()) * fraction;
        double h = v0.getHeight() + (v1.getHeight() - v0.getHeight()) * fraction;
        double start = v0.getAngleStart() + (v1.getAngleStart() - v0.getAngleStart()) * fraction;
        double extent = v0.getAngleExtent() + (v1.getAngleExtent() - v0.getAngleExtent()) * fraction;
        Arc2D value = (Arc2D) v0.clone();
        value.setArc(x, y, w, h, start, extent, v0.getArcType());
        return value;
    }
}

class EvaluatorQuadCurve2D extends Evaluator<QuadCurve2D> {
    public EvaluatorQuadCurve2D() {
    }

    @Override
    public QuadCurve2D evaluate(QuadCurve2D v0, QuadCurve2D v1, float fraction) {
        double x1 = v0.getX1() + (v1.getX1() - v0.getX1()) * fraction;
        double y1 = v0.getY1() + (v1.getY1() - v0.getY1()) * fraction;
        double x2 = v0.getX2() + (v1.getX2() - v0.getX2()) * fraction;
        double y2 = v0.getY2() + (v1.getY2() - v0.getY2()) * fraction;
        double ctrlx = v0.getCtrlX() + (v1.getCtrlX() - v0.getCtrlX()) * fraction;
        double ctrly = v0.getCtrlY() + (v1.getCtrlY() - v0.getCtrlY()) * fraction;
        QuadCurve2D value = (QuadCurve2D) v0.clone();
        value.setCurve(x1, y1, ctrlx, ctrly, x2, y2);
        return value;
    }
}

class EvaluatorCubicCurve2D extends Evaluator<CubicCurve2D> {
    public EvaluatorCubicCurve2D() {
    }

    @Override
    public CubicCurve2D evaluate(CubicCurve2D v0, CubicCurve2D v1, float fraction) {
        double x1 = v0.getX1() + (v1.getX1() - v0.getX1()) * fraction;
        double y1 = v0.getY1() + (v1.getY1() - v0.getY1()) * fraction;
        double x2 = v0.getX2() + (v1.getX2() - v0.getX2()) * fraction;
        double y2 = v0.getY2() + (v1.getY2() - v0.getY2()) * fraction;
        double ctrlx1 = v0.getCtrlX1() + (v1.getCtrlX1() - v0.getCtrlX1()) * fraction;
        double ctrly1 = v0.getCtrlY1() + (v1.getCtrlY1() - v0.getCtrlY1()) * fraction;
        double ctrlx2 = v0.getCtrlX2() + (v1.getCtrlX2() - v0.getCtrlX2()) * fraction;
        double ctrly2 = v0.getCtrlY2() + (v1.getCtrlY2() - v0.getCtrlY2()) * fraction;
        CubicCurve2D value = (CubicCurve2D) v0.clone();
        value.setCurve(x1, y1, ctrlx1, ctrly1, ctrlx2, ctrly2, x2, y2);
        return value;
    }
}
